using System;
using System.Globalization;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * A class that handles comparisons.
     * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
     * @author Attila Szegedi
     */
    internal sealed class ComparisonExpression : BooleanExpression
    {
        private enum ComparisonOperation
        {
            Equals,
            NotEquals,
            LessThan,
            GreaterThan,
            LessThanEquals,
            GreaterThanEquals
        }

        private readonly Expression _left;
        private readonly Expression _right;
        private readonly ComparisonOperation _operation;
        private readonly string _opString;

        internal ComparisonExpression(Expression left, Expression right, string opString)
        {
            _left = left;
            _right = right;
            // TODO opString = opString.intern();
            _opString = opString;
            switch (opString)
            {
                case "=":
                case "==":
                    _operation = ComparisonOperation.Equals;
                    break;
                case "!=":
                    _operation = ComparisonOperation.NotEquals;
                    break;
                case "&gt;":
                case ">":
                case "\\gt":
                case "gt":
                    _operation = ComparisonOperation.GreaterThan;
                    break;
                case "&gt;=":
                case ">=":
                case "\\gte":
                case "gte":
                    _operation = ComparisonOperation.GreaterThanEquals;
                    break;
                case "&lt;":
                case "<":
                case "\\lt":
                case "lt":
                    _operation = ComparisonOperation.LessThan;
                    break;
                case "&lt;=":
                case "<=":
                case "\\lte":
                case "lte":
                    _operation = ComparisonOperation.LessThanEquals;
                    break;
                default:
                    throw new ApplicationException("Unknown comparison operator " + opString);
            }
        }

        /*
         * WARNING! This algorithm is duplicated in SequenceBuiltins.modelsEqual.
         * Thus, if you update this method, then you have to update that too!
         */
        internal override bool IsTrue(Environment env)
        {
            ITemplateModel ltm = _left.GetAsTemplateModel(env);
            ITemplateModel rtm = _right.GetAsTemplateModel(env);
            if (env != null && env.IsClassicCompatible())
            {
                if (ltm == null)
                {
                    ltm = TemplateScalarModel.EmptyString;
                }
                if (rtm == null)
                {
                    rtm = TemplateScalarModel.EmptyString;
                }
            }
            AssertNonNull(ltm, _left, env);
            AssertNonNull(rtm, _right, env);
            int comp;
            if (ltm is ITemplateNumberModel && rtm is ITemplateNumberModel)
            {
                Template.Utility.Number first = EvaluationUtil.GetNumber((ITemplateNumberModel) ltm, _left, env);
                Template.Utility.Number second = EvaluationUtil.GetNumber((ITemplateNumberModel) rtm, _right, env);
                ArithmeticEngine ae =
                    env != null
                        ? env.GetArithmeticEngine()
                        : GetTemplate().GetArithmeticEngine();
                comp = ae.CompareNumbers(first, second);
            }
            else if (ltm is ITemplateDateModel && rtm is ITemplateDateModel)
            {
                var ltdm = (ITemplateDateModel) ltm;
                var rtdm = (ITemplateDateModel) rtm;
                int ltype = ltdm.GetDateType();
                int rtype = rtdm.GetDateType();
                if (ltype != rtype)
                {
                    throw new TemplateException(
                        "Can not compare dates of different type. Left date is of "
                        + TemplateDateModel.TypeNames[ltype]
                        + " type, right date is of "
                        + TemplateDateModel.TypeNames[rtype] + " type.",
                        env);
                }
                if (ltype == TemplateDateModel.Unknown)
                {
                    throw new TemplateException(
                        "Left date is of UNKNOWN type, and can not be compared.", env);
                }
                if (rtype == TemplateDateModel.Unknown)
                {
                    throw new TemplateException(
                        "Right date is of UNKNOWN type, and can not be compared.", env);
                }

                DateTimeOffset first = EvaluationUtil.GetDate(ltdm, _left, env);
                DateTimeOffset second = EvaluationUtil.GetDate(rtdm, _right, env);
                comp = first.CompareTo(second);
            }
            else if (ltm is ITemplateScalarModel && rtm is ITemplateScalarModel)
            {
                if (_operation != ComparisonOperation.Equals && _operation != ComparisonOperation.NotEquals)
                {
                    throw new TemplateException("Can not use operator " + _opString + " on string values.", env);
                }
                string first = EvaluationUtil.GetString((ITemplateScalarModel) ltm, _left, env);
                string second = EvaluationUtil.GetString((ITemplateScalarModel) rtm, _right, env);
                comp = string.Compare(first, second, env.GetLocale(), CompareOptions.None);
            }
            else if (ltm is ITemplateBooleanModel && rtm is ITemplateBooleanModel)
            {
                if (_operation != ComparisonOperation.Equals && _operation != ComparisonOperation.NotEquals)
                {
                    throw new TemplateException("Can not use operator " + _opString + " on boolean values.", env);
                }
                bool first = ((ITemplateBooleanModel) ltm).GetAsBoolean();
                bool second = ((ITemplateBooleanModel) rtm).GetAsBoolean();
                comp = (first ? 1 : 0) - (second ? 1 : 0);
            }
                // Here we handle compatibility issues
            else if (env.IsClassicCompatible())
            {
                string first = _left.GetStringValue(env);
                string second = _right.GetStringValue(env);
                comp = string.Compare(first, second, env.GetLocale(), CompareOptions.None);
            }
            else
            {
                throw new TemplateException(
                    "The only legal comparisons are between two numbers, two strings, or two dates.\n"
                    + "Left  hand operand is a " + ltm.GetType().FullName + "\n"
                    + "Right hand operand is a " + rtm.GetType().FullName + "\n"
                    , env);
            }
            switch (_operation)
            {
                case ComparisonOperation.Equals:
                    return comp == 0;
                case ComparisonOperation.NotEquals:
                    return comp != 0;
                case ComparisonOperation.LessThan:
                    return comp < 0;
                case ComparisonOperation.GreaterThan:
                    return comp > 0;
                case ComparisonOperation.LessThanEquals:
                    return comp <= 0;
                case ComparisonOperation.GreaterThanEquals:
                    return comp >= 0;
                default:
                    throw new TemplateException("unknown operation", env);
            }
        }

        public override string GetCanonicalForm()
        {
            return _left.GetCanonicalForm() + ' ' + _opString + ' ' + _right.GetCanonicalForm();
        }

        internal override bool IsLiteral()
        {
            return ConstantValue != null || (_left.IsLiteral() && _right.IsLiteral());
        }

        protected override Expression DeepCloneImpl(string name, Expression subst)
        {
            return new ComparisonExpression(_left.DeepClone(name, subst), _right.DeepClone(name, subst), _opString);
        }
    }
}